<?php

namespace common\service;

use common\model\SmsVerifyModel;
use common\model\UserModel;
use Overtrue\EasySms\EasySms;
use support\ErrorCode;

// 导入 VMS 的 client
use TencentCloud\Vms\V20200902\VmsClient;

// 导入要请求接口对应的 Request 类
use TencentCloud\Vms\V20200902\Models\SendTtsVoiceRequest;
use TencentCloud\Common\Exception\TencentCloudSDKException;
use TencentCloud\Common\Credential;

class SMSService
{
    // 短信验证码配置
    protected $smsConfig = [];

    // 单次短信有效时间，暂定10分钟有效
    protected $effectiveTime = 10;

    function __construct()
    {
        $this->smsConfig = C('SMS');
    }


    /**
     * 短信写入到数据库
     * @param $param
     * @return array|bool
     * @throws \think\Exception
     */
    protected function addSMSData($param)
    {
        $smsModel = model(SmsVerifyModel::class);
        $resData = $smsModel->addItem($param);
        if (!$resData) {
            // 写入短信失败
            throw_strack_exception($smsModel->getError(), ErrorCode::ADD_SMS_FAILURE);
        }
        return $resData;
    }

    /**
     * 判断当前IP访问次数是否超过了限制
     * @param $ip
     * @param int $limit
     * @return bool
     */
    protected function checkIpRegisterLimit($ip, $limit = 15)
    {
        $currentDayTime = get_current_day_range(time());
        $filter = [
            'ip' => $ip,
            'created' => ['between', [$currentDayTime["sdate"], $currentDayTime["edate"]]]
        ];
        $smsModel = model(SmsVerifyModel::class);
        $countSendNumber = $smsModel->where($filter)->count();
        if ($countSendNumber >= $limit) {
            return true;
        }
        return false;
    }

    /**
     * 发送短信
     * @param $param
     * @param $template
     * @param $gateway
     * @throws \Overtrue\EasySms\Exceptions\InvalidArgumentException
     */
    protected function sendSMS($param, $template, $gateway = 'qcloud')
    {
        $easySms = new EasySms($this->smsConfig);
        try {
            $easySms->send($param["phone"], [
                'template' => $this->smsConfig["template"][$template],
                'content' => $param["content"],
                'data' => $param['data'],
            ], [$gateway]);
        } catch (\Overtrue\EasySms\Exceptions\NoGatewayAvailableException $e) {
            switch ($gateway) {
                case "qcloud":
                    throw_strack_exception($e->getExceptions()[$gateway]->raw['errmsg'], ErrorCode::SMS_QCLOUD_GATEWAY_ERROR);
                    break;
                default:
                    throw_strack_exception($e->getMessage(), ErrorCode::SMS_OTHER_GATEWAY_ERROR);
                    break;
            }
        }
    }

    /**
     * 判断是否存在有效短信
     * @param $type
     * @param $phone
     * @param $ip
     * @return bool
     */
    protected function checkEffectiveSMS($type, $phone, $ip)
    {
        $smsModel = model(SmsVerifyModel::class);
        $smsData = $smsModel->field('id,expired_at')->where([
            'type' => $type,
            'ip' => $ip,
            'phone' => $phone,
            'is_invalid' => 0 // 有效的
        ])->find();

        if (!empty($smsData)) {
            // 存在则判断是否过期
            if ($smsData['expired_at'] < time()) {
                // 过期设置为无效
                $smsModel->where(['id' => $smsData['id']])->setField('is_invalid', 1);
                return true;
            }
            return false;
        }

        return true;
    }

    /**
     * 验证短信验证码
     * @param $type
     * @param $phone
     * @param $smsCode
     * @return bool
     */
    public function verifySMSCode($type, $phone, $smsCode)
    {
        // 测试环境 验证短信验证码 直接999999对比
        if (APP_DEBUG) {
            return $smsCode == "999999";
        }
        $smsModel = model(SmsVerifyModel::class);
        $smsData = $smsModel->field('id,expired_at')->where([
            'type' => $type,
            'phone' => $phone,
            'code' => $smsCode,
            'is_invalid' => 0 // 有效的
        ])->find();

        if (!empty($smsData)) {
            // 过期设置为无效
            $smsModel->where(['id' => $smsData['id']])->setField('is_invalid', 1);

            // 存在则判断是否过期
            if ($smsData['expired_at'] < time()) {
                return false;
            }
            return true;
        }

        return false;
    }


    /**
     * 发送注册短信
     * @param $phone
     * @param string $userVerifyCode
     * @return array
     * @throws \Overtrue\EasySms\Exceptions\InvalidArgumentException
     * @throws \think\Exception
     */
    public function sendRegisterSMS($phone, $userVerifyCode = '')
    {
        // 判断登录名是否重复
        $userModel = model(UserModel::class);
        $checkLoginNameUnique = $userModel->where(["phone" => $phone])->count();

        // 手机号已经被注册
        if ($checkLoginNameUnique > 0) {
            throw_strack_exception('Mobile phone number has been registered.', ErrorCode::PHONE_NUMBER_HAS_BEEN_REGISTERED);
        }

        // 验证手机号码格式错误
        if (!check_tel_number($phone)) {
            throw_strack_exception('Mobile phone number format error.', ErrorCode::PHONE_NUMBER_FORMAT_ERROR);
        }

        // 判断当前手机号码
        $ip = get_client_ip();

        // 判断是否存在有效短信
        if (!$this->checkEffectiveSMS('register', $phone, $ip)) {
            throw_strack_exception('The last message is still valid.', ErrorCode::LAST_MESSAGE_IS_STILL_VALID);
        }

        // 判断同一ip一天只能发起注册200次，暂时不限制
//        if ($this->checkIpRegisterLimit($ip, 200)) {
//            throw_strack_exception('Judge that the same IP can only initiate registration 15 times a day.', -411203);
//        }

        $verifyCode = !empty($userVerifyCode) ? $userVerifyCode : create_sms_code();
        $currentTime = time();
        $deadline = $currentTime + (60 * $this->effectiveTime); // 验证码10分钟内有效

        // 发送注册短信
        $this->sendSMS([
            'phone' => $phone,
            'content' => "您正在申请手机注册，验证码为：{1}，{2}分钟内有效！",
            'data' => [
                'code' => $verifyCode,
                'deadline' => $this->effectiveTime
            ]
        ], 'register');

        // 把验证码存入数据库
        $this->addSMSData([
            'type' => 'register',
            'phone' => $phone,
            'ip' => $ip,
            'code' => $verifyCode,
            'expired_at' => $deadline
        ]);

        return [];
    }

    /**
     * 发送登陆短信
     * @param $phone
     * @return array
     * @throws \Overtrue\EasySms\Exceptions\InvalidArgumentException
     * @throws \think\Exception
     */
    public function sendLoginSMS($phone)
    {
        // 判断登录名是否重复
        $userModel = model(UserModel::class);
        $checkLoginNameUnique = $userModel->where(["phone" => $phone])->count();

        // 当前手机号没有注册
        if ($checkLoginNameUnique <= 0) {
            throw_strack_exception('The current mobile phone number is not registered.', ErrorCode::MOBILE_PHONE_NUMBER_IS_NOT_REGISTERED);
        }

        // 验证手机号码格式错误
        if (!check_tel_number($phone)) {
            throw_strack_exception('Mobile phone number format error.', ErrorCode::PHONE_NUMBER_FORMAT_ERROR);
        }

        // 判断当前手机号码
        $ip = get_client_ip();

        // 判断是否存在有效短信
        if (!$this->checkEffectiveSMS('login', $phone, $ip)) {
            throw_strack_exception('The last message is still valid.', ErrorCode::LAST_MESSAGE_IS_STILL_VALID);
        }

        // 判断同一ip一天只能发起注册200次，暂时不限制
//        if ($this->checkIpRegisterLimit($ip, 200)) {
//            throw_strack_exception('Judge that the same IP can only initiate registration 15 times a day.', -411203);
//        }

        $verifyCode = create_sms_code();
        $currentTime = time();
        $deadline = $currentTime + (60 * $this->effectiveTime); // 验证码10分钟内有效

        // 发送注册短信
        $this->sendSMS([
            'phone' => $phone,
            'content' => "验证码为：{1}，{2}分钟内有效！您正在登录，若非本人操作，请勿泄露。",
            'data' => [
                'code' => $verifyCode,
                'deadline' => $this->effectiveTime
            ]
        ], 'login');

        // 把验证码存入数据库
        $this->addSMSData([
            'type' => 'login',
            'phone' => $phone,
            'ip' => $ip,
            'code' => $verifyCode,
            'expired_at' => $deadline
        ]);

        return [];
    }


    /**
     * 发送找回密码短信
     * @param $phone
     * @return array
     * @throws \Overtrue\EasySms\Exceptions\InvalidArgumentException
     * @throws \think\Exception
     */
    public function sendForgotSMS($phone)
    {
        // 判断登录名是否重复
        $userModel = model(UserModel::class);
        $checkLoginNameUnique = $userModel->where(["phone" => $phone])->count();

        // 当前手机号没有注册
        if ($checkLoginNameUnique <= 0) {
            throw_strack_exception('The current mobile phone number is not registered.', ErrorCode::MOBILE_PHONE_NUMBER_IS_NOT_REGISTERED);
        }

        // 验证手机号码格式错误
        if (!check_tel_number($phone)) {
            throw_strack_exception('Mobile phone number format error.', ErrorCode::PHONE_NUMBER_FORMAT_ERROR);
        }

        // 判断当前手机号码
        $ip = get_client_ip();

        // 判断是否存在有效短信
        if (!$this->checkEffectiveSMS('forgot', $phone, $ip)) {
            throw_strack_exception('The last message is still valid.', ErrorCode::LAST_MESSAGE_IS_STILL_VALID);
        }

        // 判断同一ip一天只能发起注册200次，暂时不限制
//        if ($this->checkIpRegisterLimit($ip, 200)) {
//            throw_strack_exception('Judge that the same IP can only initiate registration 15 times a day.', -411203);
//        }

        $verifyCode = create_sms_code();
        $currentTime = time();
        $deadline = $currentTime + (180 * $this->effectiveTime); // 验证码30分钟内有效

        // 发送注册短信
        $this->sendSMS([
            'phone' => $phone,
            'content' => "您的动态验证码为：{1}，{2}分钟内有效！您正在进行密码重置操作，如非本人操作，请忽略本短信！",
            'data' => [
                'code' => $verifyCode,
                'deadline' => $this->effectiveTime * 3
            ]
        ], 'forgot');

        // 把验证码存入数据库
        $this->addSMSData([
            'type' => 'forgot',
            'phone' => $phone,
            'ip' => $ip,
            'code' => $verifyCode,
            'expired_at' => $deadline
        ]);

        return [];
    }

    /**
     * 发送语音消息
     * @param $phone
     * @param $templateId
     * @param $templateContentData
     * @return array
     */
    public function sendVoiceSMS($phone, $templateId, $templateContentData)
    {
        $this->sendTenantVoiceTemplateSMS($phone, $templateId, $templateContentData);
        return ['phone' => $phone];
    }


    /**
     * 指定模板发送语音通知
     * @param $phone
     * @param string $templateId
     * @param array $templateParamSet
     * @param int $playTimes
     * @return void
     */
    public function sendTenantVoiceTemplateSMS($phone, string $templateId, array $templateParamSet, $playTimes = 2)
    {
        try {
            if (empty($this->smsConfig['tenant_voice'])) {
                throw_strack_exception('tenant voice not config file', ErrorCode::LAST_MESSAGE_IS_STILL_VALID);
            }
            $config = $this->smsConfig['tenant_voice'];
            $cred = new Credential($config['secret_id'], $config['secret_key']);

            $client = new VmsClient($cred, "ap-guangzhou", null);
            // 实例化一个 VMS 发送短信请求对象，每个接口都会对应一个 request 对象。
            $req = new SendTtsVoiceRequest();
            // 模板 ID，必须填写在控制台审核通过的模板 ID，可登陆 [语音消息控制台] 查看模板 ID
            $req->TemplateId = $templateId;
            // 模板参数，若模板没有参数，请提供为空数组
            $req->TemplateParamSet = $templateParamSet;
            /* 被叫手机号码，采用 e.164 标准，格式为+[国家或地区码][用户号码]
                * 例如：+8613711112222，其中前面有一个+号，86为国家码，13711112222为手机号 */
            $req->CalledNumber = $phone;
            // 在语音控制台添加应用后生成的实际SdkAppid，示例如1400006666
            $req->VoiceSdkAppid = $config['sdk_appid'];
            // 播放次数，可选，最多3次，默认2次
            $req->PlayTimes = $playTimes;
            // 通过 client 对象调用 SendTtsVoice 方法发起请求。注意请求方法名与请求对象是对应的
            $client->SendTtsVoice($req);

        } catch (TencentCloudSDKException $e) {
            throw_strack_exception($e->getMessage(), $e->getErrorCode());
        }
    }

    /**
     * 更换手机号，校验验证码
     * @param $type
     * @param $phone
     * @param $oldPhone
     * @param $smsCode
     * @return bool
     * @throws \Exception
     */
    public function checkChangePhoneSMSCode($type, $phone, $oldPhone, $smsCode)
    {
        // 如果有旧手机号，这种场景是更换手机号，手机号相同就不校验验证码
        if ($phone == $oldPhone) {
            return true;
        }

        if (empty($smsCode)) {
            return false;
        }

        return $this->checkSMSCode($type, $phone, $smsCode);
    }

    /**
     * 验证短信验证码
     * @param $type
     * @param $phone
     * @param $smsCode
     * @return bool
     * @throws \Exception
     */
    public function checkSMSCode($type, $phone, $smsCode)
    {
        // 测试环境 且验证码为 999999 直接通过
        if (APP_DEBUG && $smsCode == "999999") {
            return true;
        }

        $smsModel = model(SmsVerifyModel::class);
        $smsData = $smsModel->field('id,expired_at')->where([
            'type' => $type,
            'phone' => $phone,
            'code' => $smsCode,
            'is_invalid' => SmsVerifyModel::IS_INVALID_NO // 有效的
        ])->find();

        if (!empty($smsData)) {
            // 过期设置为无效
            $smsModel->where(['id' => $smsData['id']])->setField('is_invalid', SmsVerifyModel::IS_INVALID_YES);

            // 存在则判断是否过期
            if ($smsData['expired_at'] < time()) {
                return false;
            }
            return true;
        }

        return false;
    }
}